package task.smartant;

import java.util.ArrayList;
import task.AbstractAutomatonTask;
import task.config.AbstractAutomatonTaskConfig;
import util.Pair;
import util.Util;
import automaton.AutomatonMetaData;
import automaton.efsm.DefiniteEFSM;
import automaton.efsm.NondefiniteEFSM;

/**
 * Class implementing calculation of the fitness function
 * for the "Smart ant - 1" problem.
 * @author Daniil Chivilikhin
 */
public class SmartAntTask extends AbstractAutomatonTask {
	public SmartAnt antTask;
	private int currentState;
	private ArrayList[] actions;
	private ArrayList<String> events;
	
	public SmartAntTask(AbstractAutomatonTaskConfig config) {
		actions = new ArrayList[3];
		for (int i = 0; i < 3; i++) {
			actions[i] = new ArrayList<String>();
		}
		actions[0].add("L");
		actions[1].add("R");
		actions[2].add("M");
		events = new ArrayList<String>();
		events.add("N");
		events.add("F");
		antTask = new SmartAnt();
		this.config = config;
	}
	
	public AutomatonMetaData getFitness(DefiniteEFSM automaton) {
		antTask.reset();
		double numberOfEatenApples = 0;
		double numberOfSteps = 0;
		DefiniteEFSM dfa = new DefiniteEFSM(automaton);
		ArrayList<NondefiniteEFSM.Transition> visitedTransitions = 
			new ArrayList<NondefiniteEFSM.Transition>();
		currentState = automaton.getInitialState();
		for (int i = 0; i < SmartAnt.MAX_NUMBER_OF_STEPS; i++) {
			Pair<Integer, NondefiniteEFSM.Transition> p = makeMove(automaton);
			if (!Util.listContains(visitedTransitions, p.second)) {
				visitedTransitions.add(p.second);
			}
			numberOfEatenApples += p.first;
			numberOfSteps = i;
			if (numberOfEatenApples == SmartAnt.NUMBER_OF_APPLES) {
				break;
			}
		}
		double fitness = numberOfEatenApples + 1.0 - numberOfSteps / (double)(SmartAnt.MAX_NUMBER_OF_STEPS);
		AutomatonMetaData result = new AutomatonMetaData(dfa, visitedTransitions, fitness);
		if (numberOfEatenApples < SmartAnt.NUMBER_OF_APPLES) {
			return result;
		}
		//TODO: change "15" to the number of states in the NFA
		result.setFitness(fitness + 0.1 * (15 - result.getNumberOfVisitedStates()));
		return result;
	}
	
	@Override
	public ArrayList[] getActions() {
		return actions;
	}
	
	@Override
	public ArrayList<String> getEvents() {
		return events;
	}
	
	public Pair<Integer, NondefiniteEFSM.Transition> makeMove(DefiniteEFSM automaton) {
		int event = 0;

		if (antTask.nextIsFood()) {
			event = 1;
		}
							
    	DefiniteEFSM.Transition transition =  automaton.getTransition(currentState, event);    	    					
		int endState = transition.getEndState();
		NondefiniteEFSM.Transition nfaTransition = transition.toNFATransition();
		nfaTransition.setSymbol(event);
		currentState = endState;
		
		if (endState != -1) {
			ArrayList<String> actions = transition.getActions();
			
			if (actions.get(0).equals("L")) {
				antTask.turnLeft();
				return new Pair<Integer,NondefiniteEFSM.Transition>(0, nfaTransition);
			}
			if (actions.get(0).equals("R")) {
				antTask.turnRight();
				return new Pair<Integer, NondefiniteEFSM.Transition>(0, nfaTransition);
			}
			if (actions.get(0).equals("M")) {
				return new Pair<Integer, NondefiniteEFSM.Transition>(antTask.move(), nfaTransition);				
			}
			
			
		} 
		return new Pair<Integer, NondefiniteEFSM.Transition>(0, nfaTransition);
	}
	
	@Override
	public int getDesiredNumberOfStates() {
		return config.desiredNumberOfStates();
	}
	@Override
	public double getDesiredFitness() {
		return config.desiredFitness();
	}
	@Override
	public double getMinimumFitness() {
		return config.minimumFitness();
	}
}
